package com.tianji.coupon.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.Result;
import com.tianji.common.lock.MyLock;
import com.tianji.common.lock.MyLockStrategy;
import com.tianji.common.lock.MyLockType;
import com.tianji.common.constant.CouponCacheConstant;
import com.tianji.coupon.dto.CouponFormDTO;
import com.tianji.coupon.dto.CouponIssueFormDTO;
import com.tianji.coupon.enums.CouponStatus;
import com.tianji.coupon.enums.ObtainType;
import com.tianji.coupon.mapper.CouponMapper;
import com.tianji.coupon.mapper.CouponScopeMapper;
import com.tianji.coupon.mapper.ExchangeCodeMapper;
import com.tianji.coupon.pojo.Coupon;
import com.tianji.coupon.pojo.CouponScope;
import com.tianji.coupon.query.CouponQuery;
import com.tianji.coupon.service.CouponScopeService;
import com.tianji.coupon.service.CouponService;
import com.tianji.coupon.service.ExchangeCodeService;
import com.tianji.coupon.vo.CouponDetailVO;
import com.tianji.coupon.vo.CouponPageVO;
import com.tianji.coupon.vo.CouponScopeVO;
import com.tianji.exception.BizException;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.CopyUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;


/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author 陈佳
 * @since 2024-08-29
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {

    private final CouponScopeService couponScopeService;
    private final CouponScopeMapper  couponScopeMapper;
    private final ExchangeCodeMapper exchangeCodeMapper;
    private final ExchangeCodeService exchangeCodeService;
    private final RedisTemplate redisTemplate;



    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveCoupon(CouponFormDTO dto) {
        Coupon coupon = new Coupon();
        BeanUtils.copyProperties(dto, coupon);
        coupon.setDiscountType(dto.getDiscountType().getValue());
        coupon.setCreater(1L);
        coupon.setCreateTime(LocalDateTime.now());
        save(coupon);
        if (!dto.getSpecific()) {
            return;
        }
        Long couponId = coupon.getId();
        List<Long> scopes = dto.getScopes();
        if (CollectionUtils.isEmpty(scopes)) {
            throw new BizException("限定范围不能为空");
        }
        List<CouponScope> list = scopes.stream()
                .map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId).setType(2))
                .collect(Collectors.toList());
        couponScopeService.saveBatch(list);
    }


    @Override
    public Result queryCouponByPage(CouponQuery query) {
        Integer status = query.getStatus();
        String name = query.getName();
        Integer type = query.getType();
        Page<Coupon> page = new Page<>(query.getPage(), query.getSize());
        Page<Coupon> couponPage = lambdaQuery()
                .eq(type != null, Coupon::getDiscountType, type)
                .eq(status != null, Coupon::getStatus, status)
                .like(StringUtils.isBlank(name), Coupon::getName, name)
                .orderByDesc(Coupon::getCreateTime)
                .page(page);
        List<Coupon> records = couponPage.getRecords();
        if(CollectionUtils.isEmpty(records)){
            return Result.success(new ArrayList<>());
        }
        List<CouponPageVO> couponPageVOS = com.tianji.common.utils.BeanUtils.copyBeanList(records, CouponPageVO.class);
        return Result.success(couponPageVOS);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void beginIssue(CouponIssueFormDTO dto) {
        Coupon coupon = getById(dto.getId());
        if(coupon == null){
            throw new BizException("优惠卷不存在");
        }
        if(coupon.getStatus()!= CouponStatus.DRAFT.getValue() && coupon.getStatus() != CouponStatus.PAUSE.getValue()){
            throw new BizException("优惠卷状态错误");
        }
        LocalDateTime issueBeginTime = dto.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);
        Coupon c = com.tianji.common.utils.BeanUtils.copyBean(dto, Coupon.class);
        if(isBegin){
            c.setStatus(CouponStatus.ISSUING.getValue());
            c.setIssueBeginTime(now);
        }else{
            c.setStatus(CouponStatus.UN_ISSUE.getValue());
        }
        updateById(c);

        //添加缓存，前提是立即发放，领取优惠券的时候就不用查询数据库库存提高效率
        coupon.setIssueBeginTime(c.getIssueBeginTime());
        coupon.setIssueEndTime(c.getIssueEndTime());
        cacheCouponInfo(coupon);

        //优惠券类型必须是兑换码，状态必须是待发放才能生成兑换码
        if(coupon.getObtainWay() == ObtainType.ISSUE.getValue() && coupon.getStatus() == CouponStatus.DRAFT.getValue()){
            coupon.setIssueBeginTime(c.getIssueBeginTime());
            exchangeCodeService.asycGenerateCode(coupon);
        }
    }

    private void cacheCouponInfo(Coupon coupon) {
        //使用hash结构存储
        HashMap<String, String> hashMap = new HashMap<>(4);
        hashMap.put("issueBeginTime",String.valueOf(DateUtils.toEpochMilli(coupon.getIssueBeginTime())));
        hashMap.put("issueEndTime",String.valueOf(DateUtils.toEpochMilli(coupon.getIssueEndTime())));
        hashMap.put("totalNum",String.valueOf(coupon.getTotalNum()));
        hashMap.put("userLimit",String.valueOf(coupon.getUserLimit()));
        hashMap.put("issueNum",String.valueOf(coupon.getIssueNum()));
        redisTemplate.opsForHash().putAll(CouponCacheConstant.COUPON_CACHE_KEY_PREFIX+coupon.getId(),hashMap);
    }

    @Override
    public void updateCoupon(CouponFormDTO dto) {
        Coupon coupon = getById(dto.getId());
        if(coupon == null){
            throw new BizException("优惠卷不存在");
        }
        if(coupon.getStatus()!= CouponStatus.ISSUING.getValue()){
            throw new BizException("只有处于待发放状态的优惠券是可以修改的");
        }
        BeanUtils.copyProperties(dto, coupon, CopyUtils.getNullPropertyNames(dto));
        // 只会更新非null的字段
        updateById(coupon);
    }

    @Override
    public void deleteCoupon(Long id) {
        Coupon coupon = getById(id);
        if(coupon == null){
            throw new BizException("优惠卷不存在");
        }
        if(coupon.getStatus() != CouponStatus.ISSUING.getValue()){
            throw new BizException("只有处于待发放状态的优惠券是可以删除的");
        }
        //删除限定表数据
        couponScopeMapper.deletedByCouponId(id);
        //删除兑换码表数据
        exchangeCodeMapper.deletedByCouponId(id);
        removeById(id);
    }

    @Override
    public Result CouponByid(Long id) {
        Coupon coupon = getById(id);
        if(coupon == null){
            throw new BizException("优惠卷不存在");
        }
        CouponDetailVO couponDetailVO = new CouponDetailVO();
        BeanUtils.copyProperties(coupon, couponDetailVO);
        LambdaQueryWrapper<CouponScope> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CouponScope::getCouponId, coupon.getId());
        List<CouponScope> list = couponScopeService.list(queryWrapper);
        List<CouponScopeVO> couponScopeVOS = com.tianji.common.utils.BeanUtils.copyBeanList(list, CouponScopeVO.class);
        couponDetailVO.setScopes(couponScopeVOS);
        return Result.success(couponDetailVO);
    }

    @Override
    public Boolean couponTask(int shardIndex, int shardTotal) {
        UpdateWrapper<Coupon> updateWrapper = new UpdateWrapper<>();
        LocalDateTime currentTime = LocalDateTime.now();
        //状态是待发放并且是发放时间早于当前时间
        updateWrapper.eq("status", CouponStatus.UN_ISSUE.getValue())
                .le("issue_begin_time",currentTime)
                .last("and mod(id,"+shardTotal+") = "+shardIndex);
        Coupon coupon = new Coupon();
        coupon.setStatus(CouponStatus.ISSUING.getValue());
        boolean update = update(coupon, updateWrapper);
        return update;
    }

    @MyLock(name = "lock:pause:coupon:#{id}"
            ,lockType = MyLockType.RE_ENTRANT_LOCK
            ,lockStrategy = MyLockStrategy.FAIL_AFTER_RETRY_TIMEOUT)
    @Transactional
    @Override
    public void pauseCoupon(Long id) {
        Coupon coupon = getById(id);
        if(coupon == null){
            throw new BizException("优惠券不存在");
        }
        //当前优惠券必须是未开始或者进行中
        Integer status = coupon.getStatus();
        if(status != CouponStatus.UN_ISSUE.getValue() && status != CouponStatus.ISSUING.getValue()){
            return;
        }
        //更新状态
        boolean update = lambdaUpdate()
                .set(Coupon::getStatus, CouponStatus.PAUSE.getValue())
                .eq(Coupon::getId, id)
                .in(Coupon::getStatus, CouponStatus.UN_ISSUE.getValue(), CouponStatus.ISSUING.getValue())
                .update();
        if(!update){
            log.error("重复暂停优惠券");
        }
        //删除缓存
        redisTemplate.delete(CouponCacheConstant.COUPON_CACHE_KEY_PREFIX + id);
    }



}
